#include "mqtt_client.h"
#include "app_config.h"
#include <mosquitto.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

struct mqtt_client {
    struct mosquitto *mosq;
    mqtt_message_cb    msg_cb;
    mqtt_connect_cb    conn_cb;
    void              *user;
    mqtt_config_t      cfg; // shallow copy of pointers
    int                connected;
    int                loop_started;
    // subscriptions to re-add on reconnect
    struct sub_node *subs;
    // publish sync waiters
    int inflight_mid;
    int inflight_done;
    int ws_active;
};

static int s_lib_refcnt = 0;

struct sub_node {
    char *topic;
    int qos;
    struct sub_node *next;
};

static char *xstrdup(const char *s)
{
    if (!s) return NULL;
    size_t n = strlen(s) + 1;
    char *p = (char*)malloc(n);
    if (p) memcpy(p, s, n);
    return p;
}

static void subs_free(struct sub_node *h)
{
    while (h) {
        struct sub_node *n = h->next;
        free(h->topic);
        free(h);
        h = n;
    }
}

static void subs_add(struct mqtt_client *c, const char *topic, int qos)
{
    if (!c || !topic || !*topic) return;
    // de-dup
    for (struct sub_node *p = c->subs; p; p = p->next) {
        if (strcmp(p->topic, topic) == 0) { p->qos = qos; return; }
    }
    struct sub_node *node = (struct sub_node*)calloc(1, sizeof(*node));
    if (!node) return;
    node->topic = xstrdup(topic);
    node->qos = qos;
    node->next = c->subs;
    c->subs = node;
}

static void on_connect_bridge(struct mosquitto *mosq, void *userdata, int rc)
{
    (void)mosq;
    struct mqtt_client *c = (struct mqtt_client *)userdata;
    if (!c) return;
    c->connected = (rc == 0);
    if (rc == 0) {
        // restore subscriptions
        for (struct sub_node *p = c->subs; p; p = p->next) {
            if (p->topic) mosquitto_subscribe(c->mosq, NULL, p->topic, p->qos);
        }
    }
    if (c->conn_cb) c->conn_cb(rc, c->user);
}

static void on_disconnect_bridge(struct mosquitto *mosq, void *userdata, int rc)
{
    (void)mosq; (void)rc;
    struct mqtt_client *c = (struct mqtt_client *)userdata;
    if (!c) return;
    c->connected = 0;
}

static void on_message_bridge(struct mosquitto *mosq, void *userdata, const struct mosquitto_message *msg)
{
    (void)mosq;
    struct mqtt_client *c = (struct mqtt_client *)userdata;
    if (!c || !c->msg_cb || !msg) return;
    const char *topic = msg->topic ? msg->topic : "";
    c->msg_cb(topic, msg->payload, msg->payloadlen, c->user);
}

static void on_publish_bridge(struct mosquitto *mosq, void *userdata, int mid)
{
    (void)mosq;
    struct mqtt_client *c = (struct mqtt_client *)userdata;
    if (!c) return;
    if (mid == c->inflight_mid) c->inflight_done = 1;
}

void mqtt_config_defaults(mqtt_config_t *cfg)
{
    if (!cfg) return;
    memset(cfg, 0, sizeof(*cfg));
    cfg->host = APPCFG_DEFAULT_MQTT_HOST;
    cfg->port = APPCFG_DEFAULT_MQTT_PORT;
    cfg->client_id = NULL;
    cfg->username = NULL;
    cfg->password = NULL;
    cfg->keepalive = 60;
    cfg->clean_session = true;
    cfg->tls_enable = false;
    cfg->cafile = NULL;
    cfg->capath = NULL;
    cfg->insecure = false;
    cfg->ws_enable = false;
    cfg->ws_path = NULL;
}

static int getenv_int(const char *k, int defv)
{
    const char *v = getenv(k);
    if (!v || !*v) return defv;
    return atoi(v);
}

void mqtt_config_from_env(mqtt_config_t *cfg)
{
    if (!cfg) return;
    mqtt_config_defaults(cfg);
    const char *host = getenv("MQTT_HOST");
    if (host && *host) cfg->host = host;
    cfg->port = getenv_int("MQTT_PORT", cfg->port);
    const char *cid = getenv("MQTT_CLIENT_ID");
    if (cid && *cid) cfg->client_id = cid;
    const char *u = getenv("MQTT_USERNAME");
    if (u && *u) cfg->username = u;
    const char *p = getenv("MQTT_PASSWORD");
    if (p && *p) cfg->password = p;
    cfg->tls_enable = getenv_int("MQTT_TLS_ENABLE", 0) ? true : false;
    const char *cafile = getenv("MQTT_CAFILE");
    if (cafile && *cafile) cfg->cafile = cafile;
    const char *capath = getenv("MQTT_CAPATH");
    if (capath && *capath) cfg->capath = capath;
    cfg->insecure = getenv_int("MQTT_INSECURE", 0) ? true : false;
    cfg->ws_enable = getenv_int("MQTT_WS_ENABLE", 0) ? true : false;
    const char *wsp = getenv("MQTT_WS_PATH");
    if (wsp && *wsp) cfg->ws_path = wsp;
}

mqtt_client_t *mqtt_client_new(const mqtt_config_t *cfg_in, void *user)
{
    if (s_lib_refcnt++ == 0) {
        mosquitto_lib_init();
    }

    mqtt_config_t cfg;
    if (cfg_in) cfg = *cfg_in; else mqtt_config_defaults(&cfg);

    struct mqtt_client *c = (struct mqtt_client *)calloc(1, sizeof(*c));
    if (!c) {
        s_lib_refcnt--; // balance
        return NULL;
    }
    c->cfg = cfg; // shallow copy (caller owns strings lifetime)
    c->user = user;

    c->mosq = mosquitto_new(cfg.client_id, cfg.clean_session ? true : false, c);
    if (!c->mosq) {
        free(c);
        s_lib_refcnt--;
        return NULL;
    }

#ifdef MOSQ_OPT_PROTOCOL_VERSION
    mosquitto_int_option(c->mosq, MOSQ_OPT_PROTOCOL_VERSION, MQTT_PROTOCOL_V311);
#endif

    if (cfg.username && *cfg.username) {
        mosquitto_username_pw_set(c->mosq, cfg.username, (cfg.password && *cfg.password) ? cfg.password : NULL);
    }

    if (cfg.tls_enable) {
        int tls_rc = MOSQ_ERR_INVAL;
        // If neither cafile nor capath provided, try common system paths
        const char *caf = cfg.cafile;
        const char *cap = cfg.capath;
        if (!caf && !cap) {
            const char *candidates_file[] = {
                "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu
                "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // RHEL/CentOS
                "/etc/ssl/cert.pem", // Alpine/macOS
                NULL
            };
            const char *candidates_path[] = {
                "/etc/ssl/certs",
                "/etc/pki/tls/certs",
                NULL
            };
            // try file list first
            for (int i=0; candidates_file[i]; ++i) {
                tls_rc = mosquitto_tls_set(c->mosq, candidates_file[i], NULL, NULL, NULL, NULL);
                if (tls_rc == MOSQ_ERR_SUCCESS) { caf = candidates_file[i]; break; }
            }
            if (tls_rc != MOSQ_ERR_SUCCESS) {
                for (int i=0; candidates_path[i]; ++i) {
                    tls_rc = mosquitto_tls_set(c->mosq, NULL, candidates_path[i], NULL, NULL, NULL);
                    if (tls_rc == MOSQ_ERR_SUCCESS) { cap = candidates_path[i]; break; }
                }
            }
        } else {
            tls_rc = mosquitto_tls_set(c->mosq, caf, cap, NULL, NULL, NULL);
        }
        if (tls_rc != MOSQ_ERR_SUCCESS) {
            fprintf(stderr, "WARN: mosquitto_tls_set failed.");
            if (!caf && !cap) fprintf(stderr, " No CA path found; set MQTT_CAFILE or MQTT_CAPATH.\n");
            else fprintf(stderr, " Check MQTT_CAFILE/MQTT_CAPATH.\n");
        }
        if (cfg.insecure) mosquitto_tls_insecure_set(c->mosq, true);
    }

    // WebSocket path (requires libmosquitto built with websockets)
#ifdef MOSQ_OPT_WS_PATH
    if (cfg.ws_enable) {
        const char *path = cfg.ws_path && *cfg.ws_path ? cfg.ws_path : "/mqtt";
        if (mosquitto_string_option(c->mosq, MOSQ_OPT_WS_PATH, path) == MOSQ_ERR_SUCCESS) {
            c->ws_active = 1;
        }
    }
#else
    if (cfg.ws_enable) {
        fprintf(stderr, "WARN: libmosquitto without websockets; MQTT_WS_ENABLE ignored.\n");
    }
#endif

    mosquitto_reconnect_delay_set(c->mosq, 2, 10, true);
    mosquitto_connect_callback_set(c->mosq, on_connect_bridge);
    mosquitto_disconnect_callback_set(c->mosq, on_disconnect_bridge);
    mosquitto_message_callback_set(c->mosq, on_message_bridge);
    mosquitto_publish_callback_set(c->mosq, on_publish_bridge);

    return c;
}

void mqtt_client_destroy(mqtt_client_t *c)
{
    if (!c) return;
    if (c->loop_started) {
        mosquitto_loop_stop(c->mosq, true);
        c->loop_started = 0;
    }
    if (c->mosq) {
        mosquitto_destroy(c->mosq);
        c->mosq = NULL;
    }
    subs_free(c->subs);
    free(c);
    if (--s_lib_refcnt == 0) {
        mosquitto_lib_cleanup();
    }
}

int mqtt_client_connect(mqtt_client_t *c)
{
    if (!c || !c->mosq) return -1;
    int ret = mosquitto_connect(
        c->mosq,
        c->cfg.host ? c->cfg.host : APPCFG_DEFAULT_MQTT_HOST,
        c->cfg.port > 0 ? c->cfg.port : APPCFG_DEFAULT_MQTT_PORT,
        c->cfg.keepalive > 0 ? c->cfg.keepalive : 60);
    return ret; // 0 on success, mosq error code otherwise
}

int mqtt_client_disconnect(mqtt_client_t *c)
{
    if (!c || !c->mosq) return -1;
    return mosquitto_disconnect(c->mosq) == MOSQ_ERR_SUCCESS ? 0 : -1;
}

int mqtt_client_start(mqtt_client_t *c)
{
    if (!c || !c->mosq) return -1;
    if (c->loop_started) return 0;
    int r = mosquitto_loop_start(c->mosq);
    if (r == MOSQ_ERR_SUCCESS) { c->loop_started = 1; return 0; }
    return -1;
}

int mqtt_client_stop(mqtt_client_t *c)
{
    if (!c || !c->mosq) return -1;
    if (!c->loop_started) return 0;
    int r = mosquitto_loop_stop(c->mosq, true);
    c->loop_started = 0;
    return (r == MOSQ_ERR_SUCCESS) ? 0 : -1;
}

int mqtt_client_publish(mqtt_client_t *c, const char *topic, const void *payload, int len, int qos, int retain)
{
    if (!c || !c->mosq || !topic) return -1;
    int r = mosquitto_publish(c->mosq, NULL, topic, len, payload, qos, retain ? true : false);
    return (r == MOSQ_ERR_SUCCESS) ? 0 : -1;
}

int mqtt_client_publish_sync(mqtt_client_t *c, const char *topic, const void *payload, int len, int qos, int retain, int timeout_ms)
{
    if (!c || !c->mosq || !topic) return -1;
    int mid = 0;
    int r = mosquitto_publish(c->mosq, &mid, topic, len, payload, qos, retain ? true : false);
    if (r != MOSQ_ERR_SUCCESS) return -1;
    if (qos <= 0 || timeout_ms <= 0) return 0; // no wait
    c->inflight_mid = mid;
    c->inflight_done = 0;
    int left = timeout_ms;
    while (left > 0 && !c->inflight_done) {
        usleep(10 * 1000);
        left -= 10;
    }
    return c->inflight_done ? 0 : -2; // -2 timeout
}

int mqtt_client_subscribe(mqtt_client_t *c, const char *topic, int qos)
{
    if (!c || !c->mosq || !topic) return -1;
    subs_add(c, topic, qos);
    int r = mosquitto_subscribe(c->mosq, NULL, topic, qos);
    return (r == MOSQ_ERR_SUCCESS) ? 0 : -1;
}

void mqtt_client_set_message_cb(mqtt_client_t *c, mqtt_message_cb cb)
{
    if (!c) return;
    c->msg_cb = cb;
}

void mqtt_client_set_connect_cb(mqtt_client_t *c, mqtt_connect_cb cb)
{
    if (!c) return;
    c->conn_cb = cb;
}

int mqtt_client_is_connected(const mqtt_client_t *c)
{
    return c ? c->connected : 0;
}

void *mqtt_client_user(mqtt_client_t *c)
{
    return c ? c->user : NULL;
}

void mqtt_client_set_user(mqtt_client_t *c, void *user)
{
    if (c) c->user = user;
}

const char *mqtt_errstr(int code)
{
    return mosquitto_strerror(code);
}

int mqtt_client_uses_ws(const mqtt_client_t *c)
{
    return c ? c->ws_active : 0;
}
